package com.such.kit.io.simple;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.such.kit.Callable;
import com.such.kit.Logger;
import com.such.kit.datacast.CastCommons;
import com.such.kit.io.bean.TextIOConfig;
import com.such.kit.io.simple.support.IOSimple;
import com.such.kit.io.support.IOSource;
import com.such.kit.placeholder.PlaceholderCommons;
import com.such.kit.stream.StreamCommons;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;

/**
 * @author SUCH
 * <pre>
 * [继承扩展于 {@link IOSimple}] 的文本类型 IO 工具类
 * TextIOSimple 通过 {@link TextIOConfig} 配置 IO 特性
 * 
 * [保留字] 列表
 *   {@link #RESERVED_WORD_ROW_NUMBER}
 * </pre>
 * @see IOSimple
 */
public class TextIOSimple extends IOSimple {

	/** RESERVED_WORD_ROW_NUMBER：[保留字] [导入时] 在每条导入数据中添加此条数据在文件中的行号 */
	public static final String RESERVED_WORD_ROW_NUMBER = "ROW_NUMBER";

	private TextIOConfig config;

	/**
	 * @param config 配置
	 * @throws Exception
	 */
	public TextIOSimple(TextIOConfig config) throws Exception {
		super.charset = config.getCharset();
		this.config = config;
	}

	@Override
	public String getFileType() {
		return this.config.getFileType();
	}

	@Override
	public void input(IOSource ioSource, InputStream resourceIn) throws Exception {
		if (ValidateCommons.isNotEmpty(this.config)) {
			String mode = this.config.getMode();
			Map<String, String> modeParam = this.config.getModeParam();
			int ignoreRow = this.config.getIgnoreRow();
			String dataTemplate = this.config.getDataTemplate();
			String rowQualifier = this.config.getRowQualifier();
			List<String> ieList = this.config.getInputExpression();
			
			String charset = this.config.getCharset();
			if (ValidateCommons.isNotEmpty(charset)) {
				this.charset = charset;
			}

			List<String> fields = PlaceholderCommons.getInnerKeys(dataTemplate);
			String fieldSeparator = null;
			String fieldQualifier = null;
			boolean strictlyWidth = false;
			Map<String, Integer> fieldWidthMap = new HashMap<String, Integer>();
			if (TextIOConfig.IO_MODE_SEPARATE.equals(mode)) {
				fieldSeparator = modeParam.get(TextIOConfig.IO_MODE_PARAM_SEPARATE_SEPARATOR);
				fieldQualifier = modeParam.get(TextIOConfig.IO_MODE_PARAM_SEPARATE_QUALIFIER);
			} else if (TextIOConfig.IO_MODE_WIDTH.equals(mode)) {
				for (String key : modeParam.keySet()) {
					if (TextIOConfig.IO_MODE_PARAM_WIDTH_STRICTLY.equals(key)) {
						strictlyWidth = CastCommons.stringToBoolean(modeParam.get(key));
					} else {
						int width = Integer.valueOf(modeParam.get(key));
						fieldWidthMap.put(key, width);
					}
				}
			}

			int rowIndex = 1;
			Map<String, Object> fieldValueMap = new HashMap<String, Object>(super.args);
			fieldValueMap.put(RESERVED_WORD_ROW_NUMBER, String.valueOf(rowIndex));
			boolean rowComplete = false;
			ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream(1024);
			int fieldIndex = 0;
			int fieldQualifierIndexStart = -1;
			int fieldQualifierIndexEnd = -1;
			boolean fieldQualifierClose = true;
			int ch = 0;
			try {
				// 开始解析数据
				while ((ch = resourceIn.read()) != -1) {
					byteArrayOut.write(ch);
					String stringOut = new String(byteArrayOut.toByteArray(), super.charset);
					if (rowIndex <= ignoreRow) {
						// 跳过指定行
						if (stringOut.indexOf(rowQualifier) > -1) {
							rowIndex++;
							byteArrayOut = new ByteArrayOutputStream(1024);
						}
					} else {
						if (stringOut.indexOf(rowQualifier) > -1) {
							// 获取到行界定符则认为读取完一行数据
							rowComplete = true;
							rowIndex++;
						}
						if (!rowComplete) {
							if (TextIOConfig.IO_MODE_SEPARATE.equals(mode)) {
								// separate 模式需要一个一个处理字段
								int fieldSeparatorIndex = stringOut.lastIndexOf(fieldSeparator);
								int fieldQualifierIndex = -1;
								if (ValidateCommons.isNotEmpty(fieldQualifier)) {
									fieldQualifierIndex = stringOut.lastIndexOf(fieldQualifier);
								}
								if (fieldQualifierIndexStart == -1 && fieldQualifierClose && fieldQualifierIndex > -1) {
									/* 
									 * 当定义并匹配到了字段界定符
									 * 记录其位置
									 * fieldQualifierClose 设置为 false（界定符是否关闭）
									 */
									fieldQualifierIndexStart = fieldQualifierIndex;
									fieldQualifierClose = false;
								}
								if (fieldQualifierIndexEnd == -1 && !fieldQualifierClose && fieldQualifierIndex > fieldQualifierIndexStart) {
									/*
									 *  当定义并再次匹配到了字段界定符
									 *  记录其位置
									 *  fieldQualifierClose 重新设置为 true
									 */
									fieldQualifierIndexEnd = fieldQualifierIndex;
									fieldQualifierClose = true;
								}
								if (fieldQualifierClose && fieldSeparatorIndex > -1) {
									/*
									 * 当 fieldQualifierClose 为 true 且匹配到字段分隔符，则获取到一个字段
									 * 当 fieldQualifierIndexStart、fieldQualifierIndexEnd 不等于 -1（存在字段界定符），根据记录的 fieldQualifierIndexStart、fieldQualifierIndexEnd 截取数据
									 * 字段界定符不存在则截去字段分隔符
									 */
									if (fieldQualifierIndexStart != -1 && fieldQualifierIndexEnd != -1) {
										stringOut = stringOut.substring(fieldQualifierIndexStart + fieldQualifier.length(), fieldQualifierIndexEnd);
									} else {
										stringOut = stringOut.substring(0, fieldSeparatorIndex);
									}
									String field = fields.get(fieldIndex);
									fieldValueMap.put(field, stringOut);
									fieldIndex++;

									// 重置变量
									byteArrayOut = new ByteArrayOutputStream(1024);
									fieldQualifierIndexStart = -1;
									fieldQualifierIndexEnd = -1;
									fieldQualifierClose = true;
								}
							} else if (TextIOConfig.IO_MODE_WIDTH.equals(mode)) {
								// width 模式直接处理一行数据
							}
						} else {
							/*
							 * 读取完一行数据后
							 * separate 模式只需要处理最后一个无字段分隔符的字段
							 * width 模式则直接截取数据
							 */
							if (TextIOConfig.IO_MODE_SEPARATE.equals(mode)) {
								if (fieldQualifierClose) {
									if (fieldQualifierIndexStart != -1 && fieldQualifierIndexEnd != -1) {
										stringOut = stringOut.substring(fieldQualifierIndexStart + fieldQualifier.length(), fieldQualifierIndexEnd);
									}
									String field = fields.get(fieldIndex);
									fieldValueMap.put(field, stringOut);
								}
							} else if (TextIOConfig.IO_MODE_WIDTH.equals(mode)) {
								for (String field : fields) {
									int width = fieldWidthMap.get(field);
									String value = StringCommons.substring(stringOut, 0, width, super.charset, strictlyWidth);
									fieldValueMap.put(field, value);
									stringOut = StringCommons.substring(stringOut, width, stringOut.length(), super.charset, strictlyWidth);
								}
							}
							for (String ie : ieList) {
								ioSource.doInput(ie, fieldValueMap, false, new HashMap<String, Object>());
							}

							// 重置变量
							fieldValueMap = new HashMap<String, Object>(super.args);
							fieldValueMap.put(RESERVED_WORD_ROW_NUMBER, String.valueOf(rowIndex));
							rowComplete = false;
							byteArrayOut = new ByteArrayOutputStream(1024);
							fieldIndex = 0;
							fieldQualifierIndexStart = -1;
							fieldQualifierIndexEnd = -1;
							fieldQualifierClose = true;
						}
					}
				}
				ioSource.doInput(null, null, true, null);
			} finally {
				StreamCommons.close(resourceIn);
				StreamCommons.close(byteArrayOut);
			}
		}
	}

	@Override
	public void output(IOSource ioSource, OutputStream resourceOut) throws Exception {
		if (ValidateCommons.isNotEmpty(this.config)) {
			List<String> titleTemplate = this.config.getTitleTemplate();
			String oe = this.config.getOutputExpression();

			// 处理表头
			if (ValidateCommons.isNotEmpty(titleTemplate)) {
				String mode = this.config.getMode();
				Map<String, String> modeParam = this.config.getModeParam();
				String dataTemplate = this.config.getDataTemplate();
				String rowQualifier = this.config.getRowQualifier();

				List<String> fields = PlaceholderCommons.getInnerKeys(dataTemplate);
				int titleSize = titleTemplate.size();
				int fieldSize = fields.size();
				if (titleSize == fieldSize) {
					Map<String, Object> param = new HashMap<String, Object>();
					for (int i = 0; i < titleSize; i++) {
						param.put(fields.get(i), titleTemplate.get(i));
					}
					if (TextIOConfig.IO_MODE_WIDTH.equals(mode)) {
						balanceWidth(param, modeParam);
					}
					resourceOut.write(PlaceholderCommons.replacePlaceholder(dataTemplate, param).getBytes(super.charset));
					resourceOut.write(rowQualifier.getBytes(super.charset));
				} else {
					Logger.warn(getClass(), "表头列数与数据列数不相等，生成空表头");
					resourceOut.write(rowQualifier.getBytes(super.charset));
				}
			}
			/*
			 * 取出 dataTemplate，将占位符替换输出
			 * 输出内容后再将行界定符输出
			 * 对于等宽模式，需要加工数据，为不够位的数据填充空格
			 */
			ioSource.doOutput(oe, new HashMap<String, Object>(super.args), new OutputCallable(config, resourceOut));
		}
	}

	private class OutputCallable implements Callable<Map<String, Object>, Boolean> {

		private TextIOConfig config;
		private OutputStream out;

		public OutputCallable(TextIOConfig config, OutputStream out) {
			this.config = config;
			this.out = out;
		}

		@Override
		public Boolean call(Map<String, Object> param) throws Exception {
			String mode = this.config.getMode();
			Map<String, String> modeParam = this.config.getModeParam();
			String dataTemplate = this.config.getDataTemplate();
			String rowQualifier = this.config.getRowQualifier();

			if (TextIOConfig.IO_MODE_WIDTH.equals(mode)) {
				balanceWidth(param, modeParam);
			}
			String rowResource = PlaceholderCommons.replacePlaceholder(dataTemplate, param);
			this.out.write(rowResource.getBytes(charset));
			this.out.write(rowQualifier.getBytes(charset));
			return true;
		}

	}

	private void balanceWidth(Map<String, Object> param, Map<String, String> modeParam) {
		boolean useStrictlyLength = false;
		Object strictly = modeParam.get(TextIOConfig.IO_MODE_PARAM_WIDTH_STRICTLY);
		if (ValidateCommons.isNotEmpty(strictly)) {
			useStrictlyLength = CastCommons.stringToBoolean(String.valueOf(strictly));
		}
		for (String key : modeParam.keySet()) {
			if (!TextIOConfig.IO_MODE_PARAM_WIDTH_STRICTLY.equals(key)) {
				int width = Integer.valueOf(modeParam.get(key));
				String value = String.valueOf(param.get(key));
				value = StringCommons.formatStringLength(value, width, super.charset, useStrictlyLength);
				param.put(key, value);
			}
		}
	}

}
